/*
** An tokenizer for SQL
** This file contains C code that implements the system_complete() API. This code used to be part of the tokenizer.c source file.  But by
** separating it out, the code will be automatically omitted from static links that do not use it.
*/
#include "../System/System.h"
#ifndef PARSE_OMIT_COMPLETE

// This is defined in tokenize.c.  We just have to import the definition.
#ifndef SYSTEM_AMALGAMATION
#ifdef SYSTEM_ASCII
#define IdChar(C)  ((systemCtypeMap[(unsigned char)C]&0x46)!=0)
#endif
#ifdef SYSTEM_EBCDIC
extern const char systemIsEbcdicIdChar[];
#define IdChar(C)  (((c=C)>=0x42 && sqlite3IsEbcdicIdChar[c-0x40]))
#endif
#endif

/*
** Token types used by the parse_complete() routine.  See the header comments on that procedure for additional information.
*/
#define tkSEMI    0
#define tkWS      1
#define tkOTHER   2
#ifndef SYSTEM_OMIT_TRIGGER
#define tkEXPLAIN 3
#define tkCREATE  4
#define tkTEMP    5
#define tkTRIGGER 6
#define tkEND     7
#endif

/*
** Return TRUE if the given SQL string ends in a semicolon.
** Special handling is require for CREATE TRIGGER statements. Whenever the CREATE TRIGGER keywords are seen, the statement must end with ";END;".
** This implementation uses a state machine with 8 states:
**
**   (0) INVALID   We have not yet seen a non-whitespace character.
**   (1) START     At the beginning or end of an SQL statement.  This routine returns 1 if it ends in the START state and 0 if it ends
**                 in any other state.
**   (2) NORMAL    We are in the middle of statement which ends with a single semicolon.
**   (3) EXPLAIN   The keyword EXPLAIN has been seen at the beginning of  a statement.
**   (4) CREATE    The keyword CREATE has been seen at the beginning of a statement, possibly preceeded by EXPLAIN and/or followed by
**                 TEMP or TEMPORARY
**   (5) TRIGGER   We are in the middle of a trigger definition that must be ended by a semicolon, the keyword END, and another semicolon.
**   (6) SEMI      We've seen the first semicolon in the ";END;" that occurs at the end of a trigger definition.
**   (7) END       We've seen the ";END" of the ";END;" that occurs at the end of a trigger difinition.
**
** Transitions between states above are determined by tokens extracted from the input.  The following tokens are significant:
**
**   (0) tkSEMI      A semicolon.
**   (1) tkWS        Whitespace.
**   (2) tkOTHER     Any other SQL token.
**   (3) tkEXPLAIN   The "explain" keyword.
**   (4) tkCREATE    The "create" keyword.
**   (5) tkTEMP      The "temp" or "temporary" keyword.
**   (6) tkTRIGGER   The "trigger" keyword.
**   (7) tkEND       The "end" keyword.
**
** Whitespace never causes a state transition and is always ignored. This means that a SQL string of all whitespace is invalid.
**
** If we compile with SYSTEM_OMIT_TRIGGER, all of the computation needed to recognize the end of a trigger can be omitted.  All we have to do
** is look for a semicolon that is not part of an string or comment.
*/
int parse_complete(const char *zSql)
{
	u8 state = 0;   // Current state, using numbers defined in header comment
	u8 token;       // Value of the next token
#ifndef PARSE_OMIT_TRIGGER
  // A complex statement machine used to detect the end of a CREATE TRIGGER statement.  This is the normal case.
  static const u8 trans[8][8] = {
                     /* Token:                                                */
     /* State:       **  SEMI  WS  OTHER  EXPLAIN  CREATE  TEMP  TRIGGER  END */
     /* 0 INVALID: */ {    1,  0,     2,       3,      4,    2,       2,   2, },
     /* 1   START: */ {    1,  1,     2,       3,      4,    2,       2,   2, },
     /* 2  NORMAL: */ {    1,  2,     2,       2,      2,    2,       2,   2, },
     /* 3 EXPLAIN: */ {    1,  3,     3,       2,      4,    2,       2,   2, },
     /* 4  CREATE: */ {    1,  4,     2,       2,      2,    4,       5,   2, },
     /* 5 TRIGGER: */ {    6,  5,     5,       5,      5,    5,       5,   5, },
     /* 6    SEMI: */ {    6,  6,     5,       5,      5,    5,       5,   7, },
     /* 7     END: */ {    1,  7,     5,       5,      5,    5,       5,   5, },
  };
#else
  // If triggers are not supported by this compile then the statement machine used to detect the end of a statement is much simplier
  static const u8 trans[3][3] = {
                     /* Token:           */
     /* State:       **  SEMI  WS  OTHER */
     /* 0 INVALID: */ {    1,  0,     2, },
     /* 1   START: */ {    1,  1,     2, },
     /* 2  NORMAL: */ {    1,  2,     2, },
  };
#endif
	while (*zSql)
	{
		switch (*zSql)
		{
			case ';':
			{
				// A semicolon
				token = tkSEMI;
				break;
			}
			case ' ':
			case '\r':
			case '\t':
			case '\n':
			case '\f':
			{
				// White space is ignored
				token = tkWS;
				break;
			}
			case '/':
			{
				// C-style comments
				if (zSql[1]!='*')
				{
					token = tkOTHER;
					break;
				}
				zSql += 2;
				while (zSql[0] && (zSql[0]!='*' || zSql[1]!='/'))
					zSql++;
				if (zSql[0]==0)
					return 0;
				zSql++;
				token = tkWS;
				break;
			}
			case '-':
			{   
				// SQL-style comments from "--" to end of line
				if (zSql[1]!='-')
				{
					token = tkOTHER;
					break;
				}
				while (*zSql && *zSql!='\n')
					zSql++;
				if (*zSql==0)
					return (state==1);
				token = tkWS;
				break;
			}
			case '[':
			{  
				// Microsoft-style identifiers in [...]
				zSql++;
				while (*zSql && *zSql!=']')
					zSql++;
				if (*zSql==0)
					return 0;
				token = tkOTHER;
				break;
			}
			case '`':
			case '"':
			case '\'':
			{
				// Grave-accent quoted symbols used by MySQL or single- and double-quoted strings
				int c = *zSql;
				zSql++;
				while (*zSql && *zSql!=c)
					zSql++;
				if (*zSql==0)
					return 0;
				token = tkOTHER;
				break;
			}
			default:
			{
#ifdef SYSTEM_EBCDIC
				unsigned char c;
#endif
				if (IdChar((u8)*zSql))
				{
					// Keywords and unquoted identifiers
					int nId;
					for (nId=1; IdChar(zSql[nId]); nId++) {}
#ifdef PARSE_OMIT_TRIGGER
					token = tkOTHER;
#else
					switch (*zSql)
					{
						case 'c': case 'C':
						{
							if (nId==6 && systemStrNICmp(zSql, "create", 6)==0)
								token = tkCREATE;
							else
								token = tkOTHER;
							break;
						}
						case 't': case 'T':
						{
							if (nId==7 && systemStrNICmp(zSql, "trigger", 7)==0)
								token = tkTRIGGER;
							else if (nId==4 && systemStrNICmp(zSql, "temp", 4)==0)
								token = tkTEMP;
							else if (nId==9 && systemStrNICmp(zSql, "temporary", 9)==0)
								token = tkTEMP;
							else
								token = tkOTHER;
							break;
						}
						case 'e':  case 'E':
						{
							if (nId==3 && systemStrNICmp(zSql, "end", 3)==0)
								token = tkEND;
							else
#ifndef SYSTEM_OMIT_EXPLAIN
							if (nId==7 && systemStrNICmp(zSql, "explain", 7)==0)
								token = tkEXPLAIN;
							else
#endif
								token = tkOTHER;
							break;
						}
						default:
						{
							token = tkOTHER;
							break;
						}
					}
#endif /* PARSE_OMIT_TRIGGER */
					zSql += nId-1;
				} else
					// Operators and special symbols
					token = tkOTHER;
				break;
			}
		}
		state = trans[state][token];
		zSql++;
	}
	return (state==1);
}

#ifndef SYSTEM_OMIT_UTF16
/*
** This routine is the same as the parse_complete() routine described above, except that the parameter is required to be UTF-16 encoded, not UTF-8.
*/
int parse_complete16(const void *zSql)
{
	system_value *pVal;
	char const *zSql8;
	int rc = SYSTEM_NOMEM;
#ifndef SYSTEM_OMIT_AUTOINIT
	rc = system_initialize();
	if (rc)
		return rc;
#endif
	pVal = systemValueNew(0);
	systemValueSetStr(pVal, -1, zSql, SYSTEM_UTF16NATIVE, SYSTEM_STATIC);
	zSql8 = systemValueText(pVal, SYSTEM_UTF8);
	if (zSql8)
		rc = parse_complete(zSql8);
	else
		rc = SYSTEM_NOMEM;
	systemValueFree(pVal);
	return systemApiExit(0, rc);
}
#endif /* SYSTEM_OMIT_UTF16 */
#endif /* PARSE_OMIT_COMPLETE */
